#pragma once
#include<iostream>
#include<string>
#include<cstring>
#include<signal.h>
#include<unistd.h>
#include<memory>
#include<cerrno>
#include<cassert>
#include<pthread.h>
#include <sys/wait.h>
#include<sys/types.h>
#include<sys/socket.h>
#include<arpa/inet.h>
#include "ThreadPool/log.hpp"
#include "ThreadPool/threadPool.hpp"
#include "ThreadPool/Task.hpp"

static void service(int sock, const std::string& clientip, const uint16_t& clientport, const std::string& name) // 【方式五】调service,多了一个_name参数
{
    //echo server
    char buffer[1024];
    while(true)
    {
        // read && write 可以直接被使用！
        ssize_t s = read(sock, buffer, sizeof(buffer)-1); // 相当于udp recvfrom
        if(s > 0)
        {
            buffer[s] = 0; //将发过来的数据当做字符串
            std::cout << clientip << ":" << clientport << "# " << buffer << std::endl;
        }
        else if(s == 0) //对端关闭连接
        {
            logMessage(NORMAL, "%s:%d shutdown, me too!", clientip.c_str(), clientport);
            break;
        }
        else
        { 
            logMessage(ERROR, "read socket error, %d:%s", errno, strerror(errno));
            break;
        }

        write(sock, buffer, strlen(buffer)); // 先当于 udp sendto
    }
    close(sock); // 这里的sock是serversock，一个客户端对应一个，当不再与这个客户端通信,需要关闭
}

// // ThreadData对应【方式4】
// class ThreadData
// {
// public:
//     std::string _ip;
//     uint16_t _port;
//     int _serversock;
// };

class TcpServer
{
private:
    const static int gbacklog = 20; // 后面再说

    // threadRoutine函数对应【方式4】 【方式5】不需要
    // static void *threadRoutine(void *args)
    // {
    //     pthread_detach(pthread_self());
    //     ThreadData *td = static_cast<ThreadData *>(args);
    //     service(td->_serversock, td->_ip, td->_port);
    //     delete td;

    //     return nullptr;
    // }

public:
    TcpServer(uint16_t port, std::string ip = "0.0.0.0")
    : _port(port),
    _ip(ip), 
    _listensock(-1),
    _threadpool_ptr(ThreadPool<Task>::getThreadPool())
    {}

    bool initServer()
    {
        // 1. 创建套接字 -- fd
        _listensock = socket(AF_INET, SOCK_STREAM, 0);
        if(_listensock < 0)
        {
            logMessage(FATAL, "creat socket error, %d, %s", errno, strerror(errno));
            exit(2);
        }
        logMessage(NORMAL, "create socket success, listensock: %d", _listensock); // 3
        
        // 2. bind -- 文件 + 网络
        struct sockaddr_in local;
        memset(&local, 0, sizeof local);
        local.sin_family = AF_INET;
        local.sin_port = htons(_port);
        local.sin_addr.s_addr = _ip.empty() ? INADDR_ANY : inet_addr(_ip.c_str());
        if (bind(_listensock, (struct sockaddr *)&local, sizeof(local)) < 0)
        {
            logMessage(FATAL, "bind error, %d:%s", errno, strerror(errno));
            exit(3);
        }

        // 3. 因为TCP是面向连接的，当正式通信的时候，需要先建立连接
        if (listen(_listensock, gbacklog) < 0)
        {
            logMessage(FATAL, "listen error, %d:%s", errno, strerror(errno));
            exit(4);
        }

        logMessage(NORMAL, "init server success");
        return true;
    }
    void start()
    {
        // signal(SIGCHLD, SIG_IGN);  // 【方式二】

        _threadpool_ptr->run();       // 【方式五】

        while (true)
        {
            // 4. 获取连接（将连接到的客户端ip和port放在src中）
            struct sockaddr_in src;
            socklen_t len = sizeof(src);
            // servicesock  和 _listensock都是套接字 但是作用不一样
            // _listensock是去获取连接的（像一个招揽工）  servicesock是去提供服务的（像一个服务员）
            // 举例: _listensock不断去招揽客人, 招揽到一个客人到店里面来了，那么就需要一个servicesock服务员去服务客人,每招揽一个，就需要一个servicesock
            // 系统角度: 这里的sock就是一个文件描述符,打印_listensock是3，每次获取到一个客户端来连接服务器,就会分配一个文件描述符给servicesock
            int servicesock = accept(_listensock, (struct sockaddr *)&src, &len);
            if (servicesock < 0)
            {
                logMessage(ERROR, "accept error, %d:%s", errno, strerror(errno));
                continue;
            }
             // 获取连接成功了
            uint16_t client_port = ntohs(src.sin_port);
            std::string client_ip = inet_ntoa(src.sin_addr);
            logMessage(NORMAL, "link success, servicesock: %d | %s : %d |\n",servicesock, client_ip.c_str(), client_port);


            //  // 【方式一】: 单进程循环 -- 只能够进行一次处理一个客户端，处理完了一个，才能处理下一个
            //  service(servicesock, client_ip, client_port);
            

            // // 【方式二】- 多进程版 --- 创建子进程
            // // 让子进程给新的连接提供服务，子进程可以打开父进程曾经打开的文件fd
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
            //     // 子进程， 子进程会继承父进程打开的文件与文件fd(servicesock与_listensock)
            //     // 子进程是来进行提供服务的，不需要监听socket，所以不需要_listensock，要关闭
            //     close(_listensock);
            //     service(servicesock, client_ip, client_port);
            //     exit(0); // 僵尸状态
            // }
            // close(servicesock); // 如果父进程关闭servicesock，不会影响子进程，且父进程用不到servicesock，所以要关闭
            // // waitpid(id, nullptr, 0); // 不能阻塞，要不然有和单线程版的server一样了
            // // 但是不阻塞，需要一个vector来存放所有子进程的pid，然后循环join,回收资源
            // // 使用vector太麻烦了,【好办法:使用信号】-- 放在start函数开始
            // // signal(SIGCHLD, SIG_IGN); // 对SIGCHLD，主动忽略SIGCHLD信号，子进程退出的时候，会自动释放自己的僵尸状态


            // // 【方式三】多进程版 --- 创建子进程 -- 不适用信号
            // pid_t id = fork();
            // assert(id != -1);
            // if(id == 0)
            // {
            //     // 子进程
            //     close(_listensock);
            //     if(fork() > 0/*子进程本身*/) exit(0); //子进程本身立即退出
            //     // 孙子进程， 孤儿进程，OS领养， OS在孤儿进程退出的时候，由OS自动回收孤儿进程！
            //     service(servicesock, client_ip, client_port);
            //     exit(0);
            // }
            // // 父进程
            // waitpid(id, nullptr, 0); //0：虽然是阻塞等待，但是不会阻塞,因为子进程已近退出了
            // close(servicesock);

            // // 【方案四】多线程
            // ThreadData *td = new ThreadData();
            // td->_serversock = servicesock;
            // td->_ip = client_ip;
            // td->_port = client_port;
            // pthread_t pid;
            // pthread_create(&pid, nullptr, threadRoutine, td);
            // // 在多线程这里不用进程关闭_listensock,但要关闭servicesock, servicesock在service里面关
            // // close(servicesock);


            // 【方案五】线程池版本的多线程
            Task t(servicesock, client_ip, client_port, service);
            _threadpool_ptr->pushTask(t);
        }

    }       
    ~TcpServer()
    {}
    
private:
    uint16_t _port;
    std::string _ip;
    int _listensock;

    // 【方案五】线程池版本的多线程 -- 需要添加一个成员
    std::unique_ptr<ThreadPool<Task>> _threadpool_ptr;
};